package com.cloud.common.feign;

import com.cloud.common.bean.SessionInfo;
import com.cloud.common.context.SessionContext;
import com.cloud.common.context.TraceContext;
import com.cloud.common.context.VersionContext;
import com.cloud.common.ribbon.RibbonBestRule;
import com.cloud.common.rpc.*;
import com.cloud.common.util.JsonUtils;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats;
import io.seata.core.context.RootContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.concurrent.TimeoutException;

@Component
public class FeignRpc {
    @Autowired
    private RPCClient rpcClient;

    /**
     * RPC调用
     *
     * @param point
     * @param feignAttribute
     * @return Object
     * @throws Exception
     */
    public Object invoke(ProceedingJoinPoint point, FeignAttribute feignAttribute) throws Exception {
        String serviceName = feignAttribute.serviceName;
        Server server = RibbonBestRule.getInstance(serviceName).doChoose();
        if (server == null) {
            throw new RuntimeException(serviceName + " service unavailable");
        }
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] parameterValues = point.getArgs();
        RPCMessage message = createMessage(feignAttribute.requestUri, parameterNames, parameterValues);

        ResponseFuture future = new ResponseFuture(message.getSequence(), feignAttribute.timeout, methodSignature.getMethod().getGenericReturnType());
        ResponseFutureManager.add(future);

        ServerStats serverStats = RibbonBestRule.getInstance(serviceName).getLoadBalancerStats().getSingleServerStat(server);
        serverStats.incrementActiveRequestsCount();
        try {
            int port = Integer.parseInt(RPCServer.SERVER_PORT_PREFIX + server.getPort());
            rpcClient.send(server.getHost(), port, message);
            Object result = future.get();
            if (result == null) {
                throw new SocketTimeoutException();
            }
            return result;
        } catch (Throwable e) {
            ResponseFutureManager.remove(future.getSequence());
            if (e instanceof SocketException || e instanceof SocketTimeoutException || e instanceof TimeoutException) {
                serverStats.incrementSuccessiveConnectionFailureCount();
                serverStats.addToFailureCount();
            }
            throw e;
        } finally {
            serverStats.decrementActiveRequestsCount();
        }
    }

    /**
     * 创建消息
     *
     * @param uri
     * @param parameterNames
     * @param parameterValues
     * @return Message
     */
    private RPCMessage createMessage(String uri, String[] parameterNames, Object[] parameterValues) {
        // 请求参数
        HashMap<String, Object> parameterMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            parameterMap.put(parameterNames[i], parameterValues[i]);
        }

        // 请求对象
        Request request = new Request();
        request.setRequestParam(JsonUtils.toJSONString(parameterMap));
        request.setRequestUri(uri);
        request.setXid(RootContext.getXID());
        request.setTraceId(TraceContext.get());
        request.setVersionInfo(VersionContext.get());
        if (SessionContext.get() != null) {
            // 只传递关键信息
            SessionInfo sessionInfo = SessionContext.get();
            SessionInfo transferSession = new SessionInfo();
            transferSession.setAppName(sessionInfo.getAppName());
            transferSession.setUserId(sessionInfo.getUserId());
            if (SessionInfo.DEBUG.equals(sessionInfo.getSecret())) {
                transferSession.setSecret(sessionInfo.getSecret());
            }
            request.setSessionInfo(JsonUtils.toJSONString(transferSession));
        }
        return new RPCMessage(JsonUtils.toJSONString(request));
    }
}
